﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;

namespace WebChatApi.Common
{
    /// <summary>
    /// Class AssemblyUtil.
    /// </summary>
    public static class AssemblyUtil
    {
        #region Static utility methods

        /// <summary>
        /// Gets the type.
        /// </summary>
        /// <param name="typeName">Name of the type.</param>
        /// <returns>Type.</returns>
        public static Type GetType(string typeName)
        {
            var assemblies = GetAppDomainAssemblies();

            if (assemblies != null)
            {
                foreach (Assembly one in assemblies)
                {
                    var type = one.GetType(typeName);
                    if (type != null)
                    {
                        return type;
                    }
                }
            }

            return null;
        }

        /// <summary>
        /// Gets the application domain assemblies.
        /// </summary>
        /// <returns>IEnumerable{Assembly}.</returns>
        public static IEnumerable<Assembly> GetAppDomainAssemblies()
        {
            return AppDomain.CurrentDomain.GetAssemblies();
        }

        /// <summary>
        /// Gets the property names.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <returns></returns>
        public static IEnumerable<string> GetPropertyNames(object obj)
        {
            IEnumerable<string> result = null;

            if (obj != null)
            {
                result = from one in obj.GetType().GetProperties() select one.Name;
            }

            return result;
        }

        /// <summary>
        /// Gets the class types from assembly.
        /// </summary>
        /// <param name="assembly">The assembly.</param>
        /// <param name="fullNames">The full names.</param>
        /// <returns>List of <see cref="Type" /> instance for specific full names.</returns>
        /// <exception cref="OperationFailureException">GetClassTypesFromAssembly</exception>
        public static List<Type> GetClassTypesByNameFromAssembly(Assembly assembly, List<string> fullNames)
        {
            List<Type> types = new List<Type>();

            if (assembly != null && fullNames != null && fullNames.Count > 0)
            {
                foreach (var name in fullNames)
                {
                    try
                    {
                        Type type = assembly.GetType(name);
                        if (type != null)
                        {
                            types.Add(type);
                        }
                    }
                    catch (Exception ex)
                    {
                        throw new OperationFailureException("GetClassTypesFromAssembly", ex, new { AssemblyName = assembly.FullName, TypeName = name });
                    }
                }
            }

            return types;
        }

        /// <summary>
        /// Gets the type of the methods from.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <param name="methodAccessFiltering">The method access filtering.</param>
        /// <returns>List{MethodInfo}.</returns>
        public static List<MethodInfo> GetMethodsFromType(Type type, MethodFiltering methodAccessFiltering)
        {
            List<MethodInfo> result = new List<MethodInfo>();

            if (type != null)
            {
                if (methodAccessFiltering == null)
                {
                    methodAccessFiltering = new MethodFiltering();
                }
                var methods = type.GetMethods();

                foreach (MethodInfo one in methods)
                {
                    if (methodAccessFiltering.IncludedAttribute == null || one.HasAttribute(methodAccessFiltering.IncludedAttribute))
                    {
                        result.Add(one);
                    }
                }
            }

            return result;
        }

        /// <summary>
        /// Gets the parameter type from method info.
        /// </summary>
        /// <param name="methodInfo">The method info.</param>
        /// <param name="index">The index.</param>
        /// <returns>Type.</returns>
        public static Type GetParameterTypeFromMethodInfo(MethodInfo methodInfo, int index = 0)
        {
            Type result = null;

            if (methodInfo != null)
            {
                var parameters = methodInfo.GetParameters();
                if (parameters != null && index < parameters.Length)
                {
                    result = parameters[index].ParameterType;
                }
            }

            return result;
        }

        /// <summary>
        /// Gets the parameter type from method info.
        /// </summary>
        /// <param name="methodInfo">The method info.</param>
        /// <param name="parameterName">Name of the parameter.</param>
        /// <returns>Type.</returns>
        public static Type GetParameterTypeFromMethodInfo(MethodInfo methodInfo, string parameterName)
        {
            Type result = null;

            if (methodInfo != null && !string.IsNullOrWhiteSpace(parameterName))
            {

                var parameters = methodInfo.GetParameters();
                result = (from one in parameters where one.Name.Equals(parameterName) select one.ParameterType).FirstOrDefault();
            }

            return result;
        }

        /// <summary>
        /// Converts to object.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value">The value.</param>
        /// <returns>``0.</returns>
        /// <exception cref="System.Exception">Failed to convert from string[\ + (string.IsNullOrWhiteSpace(value) ? string.Empty : value) + \] to object[ + typeof(T).FullName + ].</exception>
        public static T ConvertToObject<T>(this string value)
        {
            try
            {
                var converter = TypeDescriptor.GetConverter(typeof(T));
                if (converter != null)
                {
                    //Cast ConvertFromString(string text) : object to (T)
                    return (T)converter.ConvertFromString(value);
                }
                return default(T);
            }
            catch (Exception ex)
            {
                throw new Exception("Failed to convert from string[\"" + value.GetStringValue() + "\"] to object[" + typeof(T).FullName + "].", ex);
            }
        }

        /// <summary>
        /// Converts the type of to object by.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <param name="value">The value.</param>
        /// <returns>System.Object.</returns>
        public static object ConvertToObjectByType(Type type, string value)
        {
            MethodInfo method = typeof(AssemblyUtil).GetMethod("ConvertToObject");

            MethodInfo genericMethod = method.MakeGenericMethod(new Type[] { type });
            return genericMethod.Invoke(null, new object[] { value });
        }

        #endregion

        #region Extensions

        /// <summary>
        /// Determines whether the specified method has attribute.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="method">The method.</param>
        /// <returns><c>true</c> if the specified method has attribute; otherwise, <c>false</c>.</returns>
        public static bool HasAttribute<T>(this MethodInfo method) where T : Attribute
        {
            return method != null && method.GetCustomAttribute<T>() != null;
        }

        /// <summary>
        /// Determines whether the specified method has attribute.
        /// </summary>
        /// <param name="method">The method.</param>
        /// <param name="attribute">The attribute.</param>
        /// <returns><c>true</c> if the specified method has attribute; otherwise, <c>false</c>.</returns>
        public static bool HasAttribute(this MethodInfo method, Attribute attribute)
        {
            return method != null && method.GetCustomAttribute(attribute.GetType()) != null;
        }

        /// <summary>
        /// Gets the assembly version.
        /// </summary>
        /// <param name="anyObject">Any object.</param>
        /// <returns>System.String.</returns>
        public static string GetAssemblyVersion(this object anyObject)
        {
            var assembly = Assembly.GetExecutingAssembly();

            FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(assembly.Location);
            return fvi.FileVersion;
        }

        /// <summary>
        /// Gets the name of the type.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <returns>System.String.</returns>
        public static string GetTypeName(this Type type)
        {
            string result = string.Empty;

            if (type != null)
            {
                if (type.IsGenericType)
                {
                    StringBuilder builder = new StringBuilder();
                    foreach (Type t in type.GetGenericArguments())
                    {
                        builder.Append(t.GetTypeName() + ",");
                    }
                    if (builder.Length > 0)
                    {
                        builder.Remove(builder.Length - 1, 1);
                    }
                    result = string.Format("{0}<{1}>", type.Name.Remove(type.Name.IndexOf('`')), builder.ToString());
                }
                else
                {
                    result = type.Name;
                }
            }
            return result;
        }

        #endregion
    }
}
